/*
	Matt Wehinger
	CS 417

	This program creates a list of numbers and checks whether 
	or not each number is prime, and if the number is prime
	it is moved to another array to be written later.

	run: a.out num_threads arr_size
*/
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
#include <cstdlib>
using std::atoi;
#include <stdlib.h>
#include <ctime>
#include <cutil.h>
#include <cuda.h>
#include <shrUtils.h>
#include "primes_kernel_memory.cu"

#define BLOCK_SIZE 512

void initializeArray(int *, int);
void populateArray(int *, int);
void getNextSet(int *, int *, int, int);
void copyArray(int *, int *, int, int);
void printArray(int*, int); 

int main(int argc, char** argv) 
{
	srand((unsigned) time(NULL));

 	int arr_size = 0;
	int *number_list, *temp_numbers, *temp_numbers_result;
	unsigned int timer_execution = 0, timer_memcpy = 0, timer_memcpy2 = 0;//
	double  total_memcpy = 0.0, execution_time = 0.0;
	int * number_list_p;
	int iterations = 1, num_blocks, threads_per_block = 0;
	double size = 0;
	
	cudaSetDevice(1);

	arr_size = atoi(argv[1]);
	number_list = new int[arr_size];
	temp_numbers = new int[BLOCK_SIZE];
	temp_numbers_result = new int[BLOCK_SIZE];

	initializeArray(number_list, arr_size);
	populateArray(number_list, arr_size);
	initializeArray(temp_numbers, BLOCK_SIZE);
	initializeArray(temp_numbers_result, BLOCK_SIZE);
	
	size = (sizeof(int)*arr_size) / 1000000.0;
	
	//determine number of blocks that need to be used. always round up
	num_blocks = (int)(arr_size / BLOCK_SIZE) < (double)(arr_size / BLOCK_SIZE)? 1 : (int)(arr_size / BLOCK_SIZE) + 1;
	
	printf("number of blocks needed: %d\n",num_blocks);
	
	for(iterations = 0; iterations < num_blocks; iterations++)
	{
		//determine number of threads that need to be in each block, no more than BLOCK_SIZE should be needed in each block
		threads_per_block = (iterations * BLOCK_SIZE + BLOCK_SIZE) > arr_size ? arr_size - (iterations * BLOCK_SIZE) : BLOCK_SIZE;
		
		getNextSet(number_list, temp_numbers, threads_per_block, iterations);
		
		int *temp_numbers_p;

		CUDA_SAFE_CALL( cudaMalloc((void**)&temp_numbers_p, sizeof(int)*threads_per_block) );
		
		CUT_SAFE_CALL( cutCreateTimer(&timer_memcpy) );
		CUT_SAFE_CALL( cutStartTimer(timer_memcpy) );
		
		CUDA_SAFE_CALL( cudaMemcpy(temp_numbers_p, temp_numbers,  sizeof(int)*threads_per_block, cudaMemcpyHostToDevice) );
		
		CUT_SAFE_CALL( cutStopTimer(timer_memcpy) );
		
		total_memcpy += cutGetTimerValue(timer_memcpy);
		
		dim3 dimBlock(threads_per_block);
		dim3 dimGrid(1);
		
		CUT_SAFE_CALL( cutCreateTimer(&timer_execution) );
		CUT_SAFE_CALL( cutStartTimer(timer_execution) );
		findPrimes_kernel<<<dimGrid, dimBlock>>>(temp_numbers_p);
		cudaThreadSynchronize();
		
		cutStopTimer(timer_execution);
		
		execution_time += cutGetTimerValue(timer_execution) ;
		
		CUT_SAFE_CALL( cutCreateTimer(&timer_memcpy2) );
		CUT_SAFE_CALL( cutStartTimer(timer_memcpy2) );
		
		CUDA_SAFE_CALL( cudaMemcpy(temp_numbers_result, temp_numbers_p,  sizeof(int)*threads_per_block, cudaMemcpyDeviceToHost) );
		
		CUT_SAFE_CALL( cutStopTimer(timer_memcpy2) );
		
		total_memcpy += cutGetTimerValue(timer_memcpy2);

		//copyArray(temp_numbers_result, number_list, threads_per_block, iterations);
		
		cudaFree( temp_numbers_p );	
	}

	printf("Execution time: %f seconds\n", execution_time/1000.0);
	
	
	printf("time to transfer to device: %f seconds\n", total_memcpy/1000.0);

	delete[] number_list; // frees memory

	return 0;
}

void initializeArray(int *arr, int size) 
{
	for(int i_counter = 0; i_counter < size; i_counter++)
		arr[i_counter] = 0;
}

void populateArray(int *arr, int size) 
{
	for(int i_counter = 0; i_counter < size; i_counter++)
		arr[i_counter] = rand() % 1000000 + 1;
}

void getNextSet(int *arr, int *temp_arr, int threads_per_block, int iterations)
{
	int i_counter = iterations * BLOCK_SIZE;
	int max_elements = i_counter + threads_per_block;
	
	for(int t_counter = 0; i_counter < max_elements; i_counter++, t_counter++)
	{
		temp_arr[t_counter] = arr[i_counter];
	}
}

void copyArray(int *arr, int *temp_arr, int threads_per_block, int iterations)
{
	int i_counter = iterations * BLOCK_SIZE;
	int max_elements = i_counter + threads_per_block;
	
	for(int t_counter = 0; i_counter < max_elements; i_counter++, t_counter++)
	{
		temp_arr[i_counter] = arr[t_counter];
	}
}

void printArray(int* arr, int size) 
{
	for(int i_counter = 0; i_counter < size; i_counter++)
		cout << arr[i_counter] << endl;
}
